package main

import (
	"fmt"
	"log"
	"ospf/common"
	"ospf/msgs"
	"ospf/tables"
	"ospf/tools"
	"sync"
	"time"
)

type packetType uint8

const (
	// version is the OSPF version supported by this library (OSPFv2).
	version = 2

	// Fixed length structures. Note that some packets don't have constants
	// here because they only contain trailing variable length data.
	headerLen       = 16
	lsaHeaderLen    = 20
	lsaLen          = 12
	helloPackageLen = 20 // No trailing array of neighbor IDs.
	dataDescLen     = 12 // No trailing array of LSA headers.

	// package send intervals
	helloInterval        = 10
	linkExchangeInterval = 5
)

// broadCastHelloRoutine send hello packet every helloInterval seconds.
func broadCastHelloRoutine(connection *tools.Conn, selfIP common.ID, lsDB *tables.OspfLSDB) {
	for {
		helloPackage := msgs.GenHello(selfIP, lsDB)
		err := connection.BroadcastPacket(helloPackage)
		if err != nil {
			fmt.Println("broadCastHelloRoutine error:", err)
			return
		}
		time.Sleep(time.Duration(helloInterval) * time.Second)
	}
}

// linkInfoExchangeRoutine 对处于信息交换阶段的邻居发送链路信息包
func linkInfoExchangeRoutine(connection *tools.Conn, selfIP common.ID, lsDB *tables.OspfLSDB) {
	for {
		nei := lsDB.NeighT.GetNeighbors()
		for i := range nei {
			if nei[i].GetStatus() == common.TowWay {
				lsu := msgs.GenLSU(lsDB, selfIP)
				// neiID := nei[i].GetRouterId()
				// 先用广播代替，不知道为什么指定ip之后，对方接收不到.是不是lsu包相关有点问题？
				err := connection.BroadcastPacket(&lsu)
				if err != nil {
					log.Println("linkInfoExchangeRoutine error:", err)
				}
			}
		}
		time.Sleep(time.Duration(linkExchangeInterval) * time.Second)
	}
}

// Receive 接收数据包，然后分类处理
func Receive(wg *sync.WaitGroup, connection *tools.Conn, nPackage chan bool, ospfT *tables.OspfLSDB) {
	defer func() {
		wg.Done()
	}()

	for {
		p, _, _, err := connection.Read()
		if err != nil {
			log.Panicf("failed to read Packet: %v", err)
		}
		header := p.GetHeader()
		// Delete
		log.Println(header)
		switch header.MessageType {
		case msgs.HelloType:
			processHello(p, connection.GetInf(), ospfT)
		case msgs.LinkStateUpdateType:
			processLSU(p, connection, nPackage, ospfT)
		default:
		}
	}
}

func tickChecker(ru chan bool, nPackage chan bool) {
	time.Sleep(2 * time.Duration(helloInterval) * time.Second)
	t := 0
	for {
		time.Sleep(time.Duration(helloInterval) * time.Second)
		send := false
		if len(nPackage) == 0 {
			log.Println("no new package.")
			send = t == 0
		} else {
			<-nPackage
			log.Println("new package.")
			send = t > 0
		}
		if send {
			ru <- send
			t++
		}
	}
}

func routingCalculation(ru chan bool, nPackage chan bool, lsDB *tables.OspfLSDB) {
	go tickChecker(ru, nPackage)
	for {
		select {
		case update := <-ru:
			if update {
				log.Println("calculate routing.")
				lsDB.LinkStateT.Dijkstra(lsDB.RouterID, lsDB.RoutingT)
			}
		}
	}
}

func RunOspf(infNames []string, localIP []common.ID) {
	connections := tools.BuildConn(infNames)
	if len(connections) == 0 {
		log.Println("No usable interface.")
		return
	}
	var wg sync.WaitGroup
	wg.Add(1)
	// 创建数据存储结构体，维护各个表的信息，指定router-id为 localIP[0]
	ospfT := tables.NewOspfLSDB(localIP[0])

	defer wg.Wait()

	// 清理过期邻居
	go ospfT.PruneProcess()

	// 判断是否需要更新路由
	routingUpdate := make(chan bool, 1)
	nPackage := make(chan bool, 1)

	for i, connection := range connections {
		go Receive(&wg, connection, nPackage, ospfT)
		go broadCastHelloRoutine(connection, localIP[i], ospfT)
		go linkInfoExchangeRoutine(connection, localIP[i], ospfT)
		go routingCalculation(routingUpdate, nPackage, ospfT)
		go ShowInfo(ospfT)
	}
}

func ShowInfo(t *tables.OspfLSDB) {
	for {
		fmt.Println(t.LinkStateT.String())
		time.Sleep(time.Duration(linkExchangeInterval) * time.Second)
	}
}
